package logcore

import (
	"fmt"
	"log"
	"mylogger/TLog"
	"mylogger/TLog/buffer"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var (

	// qualified package name, cached at first use
	logrusPackage string

	// Positions in the call stack when tracing to report the calling method
	minimumCallerDepth int

	// Used for caller information initialisation
	callerInitOnce sync.Once
)

const (
	maximumCallerDepth int = 25
	knownLogrusFrames  int = 4
)

type Entry struct {
	Logger    *TLog.Logger
	Time      time.Time
	Level     TLog.Level
	LevelName string
	Name      string
	// Calling method, with package name
	Caller  *runtime.Frame
	Message string
	// When formatter is called in entry.log(), a Buffer may be set to entry
	Buffer *buffer.Buffer
	// err may contain a field formatting error
	err string
}

func NewEntry(logger *TLog.Logger, name string) *Entry {

	return &Entry{
		Logger: logger,
		Name:   name,
		// Default is three fields, plus one optional.  Give a little extra room.
	}
}

func DefaultEntry() *Entry {
	logger := TLog.DefaultLogger()
	logger.SetLevel(TLog.LevelDebug)
	logger.SetFlag(TLog.LDateTime | TLog.Lcaller | TLog.PrefixHead | TLog.Lshortfile)
	return &Entry{}
}

func (entry Entry) HasCaller() (has bool) {
	return entry.Logger != nil &&
		entry.Logger.IsCaller &&
		entry.Caller != nil
}

func getPackageName(f string) string {
	for {
		lastPeriod := strings.LastIndex(f, ".")
		lastSlash := strings.LastIndex(f, "/")
		if lastPeriod > lastSlash {
			f = f[:lastPeriod]
		} else {
			break
		}
	}

	return f
}

func getCaller() *runtime.Frame {
	// cache this package's fully-qualified name
	callerInitOnce.Do(func() {
		pcs := make([]uintptr, maximumCallerDepth)
		_ = runtime.Callers(0, pcs)

		// dynamic get the package name and the minimum caller depth
		for i := 0; i < maximumCallerDepth; i++ {
			funcName := runtime.FuncForPC(pcs[i]).Name()
			log.Println("getCaller", i, funcName)
			if strings.Contains(funcName, "getCaller") {
				logrusPackage = getPackageName(funcName)
				log.Println("getPackageName", i, logrusPackage)
				break
			}
		}

		minimumCallerDepth = knownLogrusFrames
	})

	// Restrict the lookback frames to avoid runaway lookups
	pcs := make([]uintptr, maximumCallerDepth)
	depth := runtime.Callers(minimumCallerDepth, pcs)
	frames := runtime.CallersFrames(pcs[:depth])
	for i := 0; i < maximumCallerDepth; i++ {
		f, again := frames.Next()
		//for f, again := frames.Next(); again; f, again = frames.Next() {
		log.Println("getCaller for--", f.Function, again)
		pkg := getPackageName(f.Function)
		// If the caller isn't part of this package, we're done
		if pkg != logrusPackage {
			return &f //nolint:scopelint
		}
	}

	// if we got here, we failed to find the caller's context
	return nil
}

func (entry *Entry) log(level TLog.Level, msg string) {

	if entry.Time.IsZero() {
		entry.Time = time.Now()
	}
	entry.Level = level
	entry.LevelName = TLog.LevelName[int(level)]
	entry.Message = msg
	if entry.Logger.IsCaller {
		entry.Caller = getCaller()
	}
	entry.write()
}

func (entry *Entry) write() {
	s, err := entry.Logger.Formatter.Format(entry)
	if err != nil {
		log.Println(err.Error())
		return
	}
	_, err = entry.Logger.Handler.Write(s)
	if err != nil {
		return
	}

}

func (entry *Entry) Log(level TLog.Level, args ...interface{}) {
	if atomic.LoadUint32((*uint32)(&entry.Logger.Level)) < uint32(level) {
		entry.log(level, fmt.Sprint(args...))
	}
}

func (entry *Entry) Trace(args ...interface{}) {
	entry.Log(TLog.LevelTrace, args...)
}

func (entry *Entry) Debug(args ...interface{}) {
	entry.Log(TLog.LevelDebug, args...)
}

func (entry *Entry) Print(args ...interface{}) {
	entry.Info(args...)
}

func (entry *Entry) Info(args ...interface{}) {
	entry.Log(TLog.LevelInfo, args...)
}

func (entry *Entry) Warn(args ...interface{}) {
	entry.Log(TLog.LevelWarn, args...)
}

func (entry *Entry) Warning(args ...interface{}) {
	entry.Warn(args...)
}

func (entry *Entry) Error(args ...interface{}) {
	entry.Log(TLog.LevelError, args...)
}

func (entry *Entry) Fatal(args ...interface{}) {
	entry.Log(TLog.LevelFatal, args...)
	//entry.Logger.Exit(1)
}

func (entry *Entry) Panic(args ...interface{}) {
	entry.Log(TLog.LevelPanic, args...)
}

// Entry Printf family functions

func (entry *Entry) Logf(level TLog.Level, format string, args ...interface{}) {
	if atomic.LoadUint32((*uint32)(&entry.Level)) > uint32(level) {
		entry.Log(level, fmt.Sprintf(format, args...))
	}
}

func (entry *Entry) Tracef(format string, args ...interface{}) {
	entry.Logf(TLog.LevelTrace, format, args...)
}

func (entry *Entry) Debugf(format string, args ...interface{}) {
	entry.Logf(TLog.LevelDebug, format, args...)
}

func (entry *Entry) Infof(format string, args ...interface{}) {
	entry.Logf(TLog.LevelInfo, format, args...)
}

func (entry *Entry) Printf(format string, args ...interface{}) {
	entry.Infof(format, args...)
}

func (entry *Entry) Warnf(format string, args ...interface{}) {
	entry.Logf(TLog.LevelWarn, format, args...)
}

func (entry *Entry) Warningf(format string, args ...interface{}) {
	entry.Warnf(format, args...)
}

func (entry *Entry) Errorf(format string, args ...interface{}) {
	entry.Logf(TLog.LevelError, format, args...)
}

func (entry *Entry) Fatalf(format string, args ...interface{}) {
	entry.Logf(TLog.LevelFatal, format, args...)
	//entry.Logger.Exit(1)
}

func (entry *Entry) Panicf(format string, args ...interface{}) {
	entry.Logf(TLog.LevelPanic, format, args...)
}
